/***********************************************************************************************************
============================================================================================================
 Bits Download Manager
 http://www.codeplex.com/BITSDownloadMgr

 Component:BITSDownloadMgr.Interop
 File Name:CopyJob.cs
 
 
============================================================================================================
 Copyright (C) 2004-2007 Microsoft Corporation

 This source is subject to the Microsoft Public License (Ms-PL).
 See http:www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
 All other rights reserved.

 THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
 OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
 LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR
 FITNESS FOR A PARTICULAR PURPOSE.
============================================================================================================
***********************************************************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using BitsDownloadMgr.Interop;
using System.Collections.ObjectModel;


namespace BitsDownloadMgr.Interop
{
	/// <summary>
	/// Facade for accessing the Bits CopyJob Class
	/// </summary>
	public sealed class CopyJob : IDisposable, IBackgroundCopyCallback
	{

		#region Events

        public event EventHandler<FilesAddedEventArgs> FilesAdded;
		private void OnFilesAdded(Collection<FileInfo> fileList)
		{
			if (FilesAdded != null)
			{
				FilesAddedEventArgs args = new FilesAddedEventArgs(fileList);
				FilesAdded.Invoke(this, args);
			}
		}
		private void OnFilesAdded(FileInfo file)
		{
			if (FilesAdded != null)
			{
				FilesAddedEventArgs args = new FilesAddedEventArgs(file);
				FilesAdded.Invoke(this, args);
			}
		}

        public event EventHandler<JobErrorEventArgs> JobError;
        private void OnJobError(IBackgroundCopyError backgroundError)
        {
            if (JobError != null)
            {
                CopyError error = new CopyError(backgroundError);
                JobError.BeginInvoke(this, new JobErrorEventArgs(error), null, null); 
            }
        }

        public event EventHandler<EventArgs> JobModification;
        private void OnJobModification()
        {
            if (JobModification != null)
            {
                UpdateProgress(); 
                JobModification.BeginInvoke(this, new EventArgs(), null, null); 
            }
        }

        public event EventHandler<EventArgs> JobTransferred;
        private void OnJobTransferred()
        {
            if (JobTransferred != null)
            {
                UpdateProgress(); 
                JobTransferred.BeginInvoke(this, new EventArgs(), null, null); 
            }
        }
		#endregion
		#region Constructors
		internal CopyJob(Guid JobId, IBackgroundCopyJob nativeJob)
		{
            SetJobInfo(JobId, nativeJob);
		}


		internal CopyJob(IBackgroundCopyJob nativeJob)
		{
            Guid JobId;
            nativeJob.GetId(out JobId);
            SetJobInfo(JobId, nativeJob); 
		}         
        private void SetJobInfo(Guid JobId, IBackgroundCopyJob nativeJob)
        {
            _jobId = JobId;
            _nativeJob = nativeJob;
            _cachedProgress = Progress;
            //UpdateProgress();
            SetNotifications();
        }
        internal void SetNotifications()
        {
            _nativeJob.SetNotifyInterface((IBackgroundCopyCallback) this);
            _nativeJob.SetNotifyFlags(
                BG_NOTIFY_FLAGS.BG_NOTIFY_JOB_MODIFICATION |
                BG_NOTIFY_FLAGS.BG_NOTIFY_JOB_TRANSFERRED |
                BG_NOTIFY_FLAGS.BG_NOTIFY_JOB_ERROR);
            
        }
		#endregion

		private IBackgroundCopyJob _nativeJob;
		#region Properties

		/// <summary>
		/// Gets/Sets a user-defined description for the job
		/// </summary>
		public string Description
		{
			get
			{
				string jobDesc;
				_nativeJob.GetDescription(out jobDesc);
				return jobDesc;
			}
			set
			{
				_nativeJob.SetDescription(value);
			}
		}

		/// <summary>
		/// Returns a list of files for the current job
		/// </summary>
		/// <returns>Array of CopyFile objects.</returns>
		public DisposableCollection<CopyFile> GetFiles()
		{
			//TODO: Write test for this method.  
			IEnumBackgroundCopyFiles filesEnum;
			_nativeJob.EnumFiles(out filesEnum);
			uint fileCount = 0;
			filesEnum.GetCount(out fileCount);
            DisposableCollection<CopyFile> jobFiles = new DisposableCollection<CopyFile>();
			for (int i = 0; i < fileCount; i++)
			{
				IBackgroundCopyFile nativeFile; 
				uint filesReturned =0;
				filesEnum.Next((uint)1, out nativeFile, out filesReturned);
				jobFiles.Add(new CopyFile(nativeFile));
			}
			return jobFiles; 
		}

		/// <summary>
		/// Gets/Sets a user-defined display name for a job. 
		/// </summary>
		public string DisplayName
		{
			get
			{
				string value;
				_nativeJob.GetDisplayName(out value);
				return value; 
			}
			set
			{
				_nativeJob.SetDisplayName(value); 
			}
		}

		public CopyError Error
		{
			get
			{
				IBackgroundCopyError nativeError;
				_nativeJob.GetError(out nativeError);
				return new CopyError(nativeError); 
			}
		}


		public uint ErrorCount
		{
			get
			{
				uint errCount = 0;
				_nativeJob.GetErrorCount(out errCount);
				return errCount; 
			}
		}
		private Guid _jobId;
		/// <summary>
		/// Gets the unique identifier for the copy job.
		/// </summary>
		public Guid Id
		{
			get { return _jobId; }
		}

		/// <summary>
		/// Gets the type of job
		/// </summary>
		public BitsJobType JobType
		{
			get
			{
				BitsJobType type;
				_nativeJob.GetType(out type);
				return type;
			}
		}

		/// <summary>
		/// Specifies the minimum length of time (in seconds) that BITS waits after encountering a transient error condition before trying to transfer the file.
		/// </summary>
		public uint MinimumRetryDelay
		{
			get
			{
				uint delay = 0;
				_nativeJob.GetMinimumRetryDelay(out delay);
				return delay; 
			}
			set
			{
				_nativeJob.SetMinimumRetryDelay(value); 
			}
		}

		/// <summary>
		/// Specifies the length of time that BITS continues to try to transfer the file after encountering a transient error condition.
		/// </summary>
		public uint NoProgressTimeout
		{
			get
			{
				uint timeout = 0;
				_nativeJob.GetNoProgressTimeout(out timeout);
				return timeout;
			}
			set
			{
				_nativeJob.SetNoProgressTimeout(value); 
			}
		}

		//Placeholder for NotifyFlags.  
		//TODO: Implement callback notification. 
		//public uint NotifyFlags

		/// <summary>
		/// Returns the NTAccount for the job's owner
		/// </summary>
		public System.Security.Principal.NTAccount Owner
		{
			get
			{
				string stringSid = "";
				_nativeJob.GetOwner(out stringSid); 
				//we have the Sid ... now what?  
				System.Security.Principal.SecurityIdentifier ownerSid = new System.Security.Principal.SecurityIdentifier(stringSid); 
				//OK ... where to now? 
				System.Security.Principal.NTAccount acct = (System.Security.Principal.NTAccount) ownerSid.Translate(typeof(System.Security.Principal.NTAccount));
				return acct; 
			}
		}

		/// <summary>
		/// Gets/Sets the priority of the job. 
		/// </summary>
		public BitsJobPriority Priority
		{
			get
			{
				BitsJobPriority currentPriority;
				_nativeJob.GetPriority(out currentPriority);
				return currentPriority;
			}
			set
			{
				_nativeJob.SetPriority(value);
			}
		}

		/// <summary>
		/// Gets information on the current progress of the job. 
		/// </summary>
		public JobProgress Progress
		{
			get
			{
				_BG_JOB_PROGRESS progress;
				_nativeJob.GetProgress(out progress);
				return new JobProgress(progress);
			}
		}

		/// <summary>
		/// Gets the current state of the copy job. 
		/// </summary>
		public BitsJobState State
		{
			get
			{
				BitsJobState jobState;
				_nativeJob.GetState(out jobState);
				return jobState;
			}
		}


		/// <summary>
		/// Sets/gets the proxy settings the job uses to transfer the files.
		/// </summary>
		public BitsProxySettings ProxySettings
		{
			//TODO: Write tests for this property.  
			get
			{
				string proxyList; string proxyBypassList;
				BitsJobProxyUsage usage;
				_nativeJob.GetProxySettings(out usage, out proxyList, out proxyBypassList);
				return new BitsProxySettings(usage, proxyList.Split(' '), proxyBypassList.Split(' ')); 
			}
			set
			{
				_nativeJob.SetProxySettings(value.ProxyUsage, String.Join(" ", value.ProxyList), String.Join(" ", value.ProxyBypassList)); 
			}
		}

		/// <summary>
		/// Gets information about times relevant to the copy job. 
		/// </summary>
		public JobTimes Times
		{
			get
			{
				_BG_JOB_TIMES nativeJobTimes;
				_nativeJob.GetTimes(out nativeJobTimes);
				return new JobTimes(nativeJobTimes); 
			}
		}


		
		/// <summary>
		/// Total number of bytes for the job
		/// </summary>
		public ulong BytesTotal
		{
			get {
				return _cachedProgress.BytesTotal; 
			}
		
		}

		

		/// <summary>
		/// Total number of bytes already transferred.  
		/// </summary>
		public ulong BytesTransferred
		{
			get { 
				return _cachedProgress.BytesTransferred ; 
			}
		
		}

		/// <summary>
		/// Total number of files in the job
		/// </summary>
		public ulong FilesTotal
		{
			get { 
				return _cachedProgress.FilesTotal; 
			}
		}

		/// <summary>
		/// Total number of files that have been completely transferred. 
		/// </summary>
		public ulong FilesTransferred
		{
			get { return _cachedProgress.FilesTransferred; }
			
		}

		private double _lastTransferRate; 
		public double LastTransferRate
		{
			get
			{
				return _lastTransferRate; 
			}
		}

		private double _averageTransferRate;
		public double AverageTransferRate
		{
			get { return _averageTransferRate; }
		}

		#endregion
		#region Methods
		JobProgress _cachedProgress = new JobProgress(new _BG_JOB_PROGRESS());
		JobProgress _previousProgress;
		System.DateTime _lastProgressUpdate = DateTime.Now;
		int _numSamples;
		double _sumLastTransferRate;
		public void UpdateProgress()
		{
            if (this.State == BitsJobState.Transferring || this.State == BitsJobState.Transferred)
            {
                _previousProgress = _cachedProgress;
                _cachedProgress = Progress;
                ulong bytesSinceLast = _cachedProgress.BytesTransferred - _previousProgress.BytesTransferred;
                TimeSpan timeSinceLast = DateTime.Now - _lastProgressUpdate;
                if (timeSinceLast.TotalSeconds > 0)
                {
                    _lastTransferRate = bytesSinceLast / timeSinceLast.TotalSeconds;
                    _lastProgressUpdate = DateTime.Now;

                    if (!_lastTransferRate.Equals(double.NaN))
                    {
                        _sumLastTransferRate += _lastTransferRate;
                        _numSamples += 1;
                        double tempAverageTransferRate = _sumLastTransferRate / _numSamples;
                        if (!tempAverageTransferRate.Equals(double.NaN))
                        {
                            _averageTransferRate = tempAverageTransferRate;
                        }
                    }
                }
         
            }
            else
            {
                _lastProgressUpdate = DateTime.Now; 
            }

		}

		public void AddFile(string remoteFileName, string localFileName)
		{
			_nativeJob.AddFile(remoteFileName, localFileName);
			OnFilesAdded(new FileInfo(remoteFileName, localFileName)); 
		}

		public void AddFiles(Collection<FileInfo> fileList)
		{
            
                _BG_FILE_INFO[] nativeFileList = new _BG_FILE_INFO[fileList.Count];
                for (int i = 0; i < fileList.Count; i++)
                {
                    nativeFileList[i] = fileList[i].GetNativeFileInfo();
                }
                _nativeJob.AddFileSet((uint)fileList.Count, nativeFileList);
                OnFilesAdded(fileList);
            
            
		}
		public void Cancel()
		{
			if (CanCancel)
			{
				_nativeJob.Cancel();
			}
		}
		public bool CanCancel
		{
			get
			{
				BitsJobState state = this.State;
				switch (state)
				{

					case BitsJobState.Acknowledged:
						return false;
						//break;
					case BitsJobState.Canceled:
						return false;
						//break;
					default:
						return true;
						//break;
				}
			}
		}
        public bool CanPause
        {
            get
            {
                switch (this.State)
                {
                    case BitsJobState.Queued:
                        return true ;
                    case BitsJobState.Connecting:
                        return true;
                    case BitsJobState.Transferring:
                        return true ;
                    case BitsJobState.Error:
                        return true ;
                    case BitsJobState.TransientError:
                        return true;
                    default:
                        return false;
                }
            }
        }
        public bool CanResume
        {
            get
            {
                switch (this.State)
                {
                    case BitsJobState.Queued:
                        return true;
                    case BitsJobState.Suspended:
                        return true;
                    case BitsJobState.TransientError:
                        return true; 
                    case BitsJobState.Error:
                        return true; 
                    default:
                        return false;
                }
            }
        }
        public bool CanComplete
        {
            get
            {
                switch (this.State)
                {
                    case BitsJobState.Canceled:
                        return false; 
                    case BitsJobState.Acknowledged:
                        return false; 
                    case BitsJobState.Connecting:
                        return false; 
                    default:
                        return true; 
                }
            }
        }
        private Exception _completeError;

        public Exception CompleteError
        {
            get { return _completeError; }
            set { _completeError = value; }
        }

		public void Complete()
		{
            try
            {
                Complete(false); 
            }
            catch
            {
                throw; 
            }
            OnJobModification(); 
		}
        public void Complete(bool force)
        {
            if (force)
            {
                try
                {
                    _nativeJob.Complete();
                }
                catch (Exception e)
                {
                    _completeError = e;
                    throw;
                }
            }
            else
            {
                if (_completeError == null)
                {
                    try
                    {
                        _nativeJob.Complete();
                    }
                    catch (Exception e)
                    {
                        _completeError = e;
                        throw;
                    }

                }
            }
        }

		public void Resume()
		{
			_nativeJob.Resume();

            OnJobModification();
		}
		public void Suspend()
		{
			_nativeJob.Suspend();
            OnJobModification(); 
		}


        
		#endregion

		#region CleanUp Code
		#region IDisposable Members

		void IDisposable.Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		#endregion
		~CopyJob()
		{
			Dispose(false);
		}
		private void Dispose(bool disposing)
		{
            if(disposing)
            {
			    if (_nativeJob != null)
			    {
                    if (State != BitsJobState.Canceled && State != BitsJobState.Acknowledged)
                    {
                        _nativeJob.SetNotifyFlags(BG_NOTIFY_FLAGS.BG_NOTIFY_DISABLE);
                        _nativeJob.SetNotifyInterface(null);
                    }
				    System.Runtime.InteropServices.Marshal.ReleaseComObject(_nativeJob);
                    
			    }
                GC.SuppressFinalize(this); 
            }
           
		}
		#endregion



        #region IBackgroundCopyCallback Members

        void IBackgroundCopyCallback.JobError(IBackgroundCopyJob pJob, IBackgroundCopyError pError)
        {
            if (pJob == _nativeJob)
            {
                OnJobError(pError); 
            }
        }

        void IBackgroundCopyCallback.JobModification(IBackgroundCopyJob pJob, int dwReserved)
        {
            
            if (pJob == _nativeJob)
            {
                OnJobModification();
            }
            else
            {
                Guid jobGuid;
                pJob.GetId(out jobGuid);
                if (jobGuid == _jobId)
                {       
                    OnJobModification(); 
                }
            }
        }

        void IBackgroundCopyCallback.JobTransferred(IBackgroundCopyJob pJob)
        {
            if (pJob == _nativeJob)
            {
                OnJobTransferred(); 
            }
            else
            {
                Guid jobGuid;
                pJob.GetId(out jobGuid);
                if (jobGuid == _jobId)
                {
                    OnJobTransferred();
                }
            }
        }

        #endregion
    }

	public sealed class FilesAddedEventArgs : EventArgs
	{
		internal FilesAddedEventArgs(Collection<FileInfo> fileList)
		{
			_fileList = fileList; 
		}
		internal FilesAddedEventArgs(FileInfo file)
		{
            _fileList = new Collection<FileInfo>();
			_fileList.Add(file); 
		}
        private Collection<FileInfo> _fileList;
        public Collection<FileInfo> FileList
		{
			get { return _fileList; }
		}

	}

    public sealed class JobErrorEventArgs : EventArgs
    {
        internal JobErrorEventArgs(CopyError error)
        {
            _jobCopyError = error; 
        }
        private CopyError _jobCopyError;
        public CopyError JobCopyError
        {
            get { return _jobCopyError; }
        }

    }
	public struct BitsProxySettings
	{
		public BitsProxySettings(BitsJobProxyUsage usage, string[] proxyList, string[] bypassList)
		{
			_proxyUsage = usage;
			_proxyList = proxyList;
			_proxyBypassList = bypassList; 
		}
        private BitsJobProxyUsage _proxyUsage;

        public BitsJobProxyUsage ProxyUsage
        {
            get { return _proxyUsage; }
            set { _proxyUsage = value; }
        }
        private string[] _proxyList;

        public string[] ProxyList
        {
            get { return _proxyList; }
            set { _proxyList = value; }
        }
		private string[] _proxyBypassList;
        public string[] ProxyBypassList
        {
            get { return _proxyBypassList; }
            set { _proxyBypassList = value; }
        }
        public override bool Equals(object obj)
        {
            if(!(obj is BitsProxySettings))
            {
                return false; 
            }
            return base.Equals((BitsProxySettings) obj);
        }
        public static bool operator ==(BitsProxySettings obj1, BitsProxySettings obj2)
        {
            return obj1.Equals(obj2); 
        }
        public static bool operator !=(BitsProxySettings obj1, BitsProxySettings obj2)
        {
            return !(obj1.Equals(obj2)); 
        }
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
	}
}
